Skip to content

Conversation

@randolf-scholz
Copy link
Contributor

Fixes #9004
See also: #11403

This PR allows set differences with disjoint generic types. This option was previously argued against (#9004 (comment)) because

A user shipped code that did set[bytes] - set[str] to production, and of course, it didn't work as expected (#1840). There is no use case for set[int] - set[str]: if your code does it, that's very likely not intentional.

However, typeshed shouldn't try to act as a linter. That's the job of the type checker. There are cases when the current setup hurts, for instance when we try to do set[Literal["a", "b"]] - set[str].

This should be handled on the type-checker side, for instance by warning on set[T] - set[S] if T and S have empty intersection (⇝ disjoint_bases https://peps.python.org/pep-0800/), which would catch the bad case described above.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

pandas (https://github.com/pandas-dev/pandas)
+ pandas/core/dtypes/cast.py:869: error: Unused "type: ignore" comment  [unused-ignore]
+ pandas/core/dtypes/cast.py:870: error: Unused "type: ignore" comment  [unused-ignore]

ibis (https://github.com/ibis-project/ibis)
- ibis/backends/sql/datatypes.py:1398: error: Unsupported operand types for - ("set[type[Never]]" and "set[type[SqlglotType]]")  [operator]
- ibis/expr/datatypes/tests/test_core.py:581: error: Unsupported operand types for - ("set[type[Never]]" and "set[AnnotableMeta]")  [operator]

schemathesis (https://github.com/schemathesis/schemathesis)
- src/schemathesis/config/_error.py:54: error: Argument 1 to "set" has incompatible type "Any | Unset"; expected "Iterable[Any | None]"  [arg-type]
+ src/schemathesis/config/_error.py:54: error: Argument 1 to "set" has incompatible type "Any | Unset"; expected "Iterable[object]"  [arg-type]

pydantic (https://github.com/pydantic/pydantic)
- pydantic/v1/main.py:924: error: Set comprehension has incompatible type Set[int | str]; expected Set[str | None]  [misc]
- pydantic/v1/main.py:924: note: Left operand is of type "set[str] | dict_keys[str, Any]"

Copy link
Collaborator

@srittau srittau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm generally not a big fan of allowing object in cases like this, but the primer output (and consistency with __isub__) speaks for itself. Also, {1,2,3} - {1,2,"ab"} is a valid use case.

@randolf-scholz
Copy link
Contributor Author

@srittau Maybe we should be using Any rather than object? This is what AbstractSet does. Might be worth it if at any point in the future, instead of object we would like to use Hashable instead.

@randolf-scholz
Copy link
Contributor Author

randolf-scholz commented Jan 16, 2026

I'd also like to get @AlexWaygood's opinion, as you discussed changing set.__sub__ a bit in #11403

@AlexWaygood
Copy link
Member

This seems like a good change to me. I agree that when typeshed is too opinionated, it often causes more problems than it solves; we've been running into a few pain points on the ty issue tracker recently with similar causes.

Please let's not (ever) change the parameter type to Hashable, though. Attempting to represent hashability in Python's type system is doomed by the fact that object.__hash__ exists, so the many types that are not hashable all violate the Liskov Substitution Principle and thus a large number of assumptions that type checkers make. This means that you get unsoundness and inconsistent behaviour from all type checkers if you annotate types with Hashable (though ty is affected more than most currently). (See astral-sh/ty#1132 (comment), astral-sh/ty#1162, astral-sh/ty#1846, PyCQA/flake8-pyi#283 (comment), etc.)

@randolf-scholz
Copy link
Contributor Author

Yeah, that whole hashability thing is a big mess. I also recently learned one can make unhashable classes via custom metaclasses, which is concerning.

Anyway, so is this good, or are there any other reasons to prefer Any vs object here? I chose object simply because that is what was already there for __isub__...

@AlexWaygood
Copy link
Member

I think either is fine; it doesn't really make much difference for parameter annotations. We sometimes use Any in parameter annotations to represent "the 'true type' is inexpressible", and I guess that applies here because of the hashability mess, but I think object is fine

@AlexWaygood AlexWaygood merged commit 236a8c0 into python:main Jan 16, 2026
63 checks passed
@AlexWaygood
Copy link
Member

Thanks @randolf-scholz!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Annotations for set.__sub__ and related methods are inconsistent

3 participants